import os
import shutil
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
import requests
from PIL import Image, ImageOps
import threading
from datetime import datetime
import time
import random  # Для генерации случайной задержки
import queue

# Пути к директориям и файлам
avatars_path = r"C:\Users\1\Desktop\VS 2022\Загрузка аватарок ВК\аватарки"
uploaded_path = os.path.join(avatars_path, "Загруженные")
tokens_file_path = r"C:\Users\1\Desktop\VS 2022\Загрузка аватарок ВК\токены для загруки аватарок.txt"
failed_accounts_file = r"C:\Users\1\Desktop\VS 2022\Загрузка аватарок ВК\неудачные.txt"

# Глобальные переменные для токенов и списка аватарок
tokens = []
avatars = []

# Параметры рамки для обработки аватарки
border_size = 110  
border_color = 'white'

# ----------------------------------------------------------------------------
# Очереди для логирования и статусы
# ----------------------------------------------------------------------------

log_queue = queue.Queue()      
success_queue = queue.Queue()    
fail_queue = queue.Queue()       

# Объект Events для паузы и возобновления
pause_event = threading.Event()
pause_event.set()  

# ----------------------------------------------------------------------------
# Создание главного окна с красивым оформлением
# ----------------------------------------------------------------------------

root = tk.Tk()
root.title("Загрузка аватарок ВК")
root.geometry("1200x600")
# Фоновый цвет главного окна (темно-синий оттенок)
root.configure(bg="#2C3E50")

# Используем ttk и настраиваем стили
style = ttk.Style(root)
style.theme_use("clam")

# Основные шрифты и цвета
default_font = ("Segoe UI", 11)
style.configure("TFrame", background="#2C3E50")
style.configure("TLabel", background="#2C3E50", foreground="#ECF0F1", font=default_font)
style.configure("TButton", font=("Segoe UI", 11, "bold"), padding=6)
style.configure("TEntry", padding=4, font=default_font)

# Для кнопок можно задать активный цвет (синий оттенок)
style.map("TButton",
          foreground=[("active", "#ECF0F1")],
          background=[("active", "#2980B9")])

# ----------------------------------------------------------------------------
# Переменные для статистики
# ----------------------------------------------------------------------------

total_accounts = tk.StringVar(value="Всего аккаунтов: 0")
processed_photos = tk.StringVar(value="Обработано фотографий: 0")
remaining_photos = tk.StringVar(value="Осталось фотографий: 0")
successful_uploads = tk.StringVar(value="Успешных добавлений: 0")
failed_uploads = tk.StringVar(value="Неуспешных добавлений: 0")

# Переменные задержки
delay_min = tk.StringVar(value="20")
delay_max = tk.StringVar(value="40")

# Цвета для логирования
INFO_COLOR = "white"
SUCCESS_COLOR = "#2ECC71"
ERROR_COLOR = "#E74C3C"
SEPARATOR_COLOR = "#3498DB"

token_counter = 0

# ----------------------------------------------------------------------------
# Глобальная переменная для автопрокрутки
# ----------------------------------------------------------------------------
auto_scroll_enabled = tk.BooleanVar(value=True)

# ----------------------------------------------------------------------------
# Функции логирования и работы с очередями
# ----------------------------------------------------------------------------

def log(message, color=INFO_COLOR):
    """Добавляем сообщение в общий лог с отметкой времени."""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_queue.put((f"[{timestamp}] {message}\n", color))

def log_success_token(token_info: str):
    """Добавляем успешный токен (только сам токен)."""
    success_queue.put(token_info)

def log_fail_token(token_info: str):
    """Добавляем неуспешный токен (только сам токен)."""
    fail_queue.put(token_info)

def process_log_queue():
    try:
        while True:
            msg, color = log_queue.get_nowait()
            log_text.insert(tk.END, msg, color)
            log_text.tag_config(color, foreground=color)
            if auto_scroll_enabled.get():
                log_text.see(tk.END)
    except queue.Empty:
        pass
    root.after(100, process_log_queue)

def process_success_queue():
    try:
        while True:
            token_info = success_queue.get_nowait()
            success_text.insert(tk.END, token_info + "\n")
            if auto_scroll_enabled.get():
                success_text.see(tk.END)
    except queue.Empty:
        pass
    root.after(100, process_success_queue)

def process_fail_queue():
    try:
        while True:
            token_info = fail_queue.get_nowait()
            fail_text.insert(tk.END, token_info + "\n")
            if auto_scroll_enabled.get():
                fail_text.see(tk.END)
    except queue.Empty:
        pass
    root.after(100, process_fail_queue)

# ----------------------------------------------------------------------------
# Функции работы с файлами и обновление информации
# ----------------------------------------------------------------------------

def log_failed_account(token):
    try:
        with open(failed_accounts_file, 'a', encoding='utf-8') as file:
            file.write(f"{token}\n")
        log(f"Записан неудачный токен: {token}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Ошибка записи неудачного токена: {str(e)}", color=ERROR_COLOR)

def load_tokens():
    global tokens
    if os.path.exists(tokens_file_path):
        with open(tokens_file_path, 'r', encoding='utf-8') as file:
            tokens = [line.strip() for line in file if line.strip()]
        total_accounts.set(f"Всего аккаунтов: {len(tokens)}")
        log(f"Загружено токенов: {len(tokens)}", color=SUCCESS_COLOR)
    else:
        log("Файл с токенами не найден.", color=ERROR_COLOR)

def load_avatars():
    global avatars
    if os.path.exists(avatars_path):
        avatars = [os.path.join(avatars_path, f)
                   for f in os.listdir(avatars_path)
                   if os.path.isfile(os.path.join(avatars_path, f)) and f != "prepared_image.png"]
        remaining_photos.set(f"Осталось фотографий: {len(avatars)}")
        log(f"Загружено аватарок: {len(avatars)}", color=SUCCESS_COLOR)
    else:
        log("Директория с аватарками не найдена.", color=ERROR_COLOR)

def update_info():
    log("Нажата кнопка 'Обновить информацию'", color=INFO_COLOR)
    load_tokens()
    load_avatars()
    processed_photos.set("Обработано фотографий: 0")
    successful_uploads.set("Успешных добавлений: 0")
    failed_uploads.set("Неуспешных добавлений: 0")
    os.makedirs(uploaded_path, exist_ok=True)
    log("Информация обновлена.", color=SUCCESS_COLOR)

# ----------------------------------------------------------------------------
# VK API-взаимодействие
# ----------------------------------------------------------------------------

def get_user_info(token):
    url = "https://api.vk.com/method/users.get"
    params = {'access_token': token, 'v': '5.131'}
    try:
        response = requests.get(url, params=params, timeout=10)
        if response.status_code == 200:
            data = response.json()
            if 'response' in data and len(data['response']) > 0:
                return data['response'][0]
        log(f"Ошибка получения информации о пользователе: {response.text}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Исключение при получении информации о пользователе: {str(e)}", color=ERROR_COLOR)
    return None

def get_upload_url(token):
    url = "https://api.vk.com/method/photos.getOwnerPhotoUploadServer"
    params = {'access_token': token, 'v': '5.131'}
    try:
        response = requests.get(url, params=params, timeout=10)
        if response.status_code == 200:
            return response.json().get('response', {}).get('upload_url')
        else:
            log(f"Ошибка получения URL: {response.text}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Исключение при получении URL: {str(e)}", color=ERROR_COLOR)
    return None

def save_photo(data, token):
    url = "https://api.vk.com/method/photos.saveOwnerPhoto"
    params = {
        'server': data.get('server'),
        'photo': data.get('photo'),
        'hash': data.get('hash'),
        'access_token': token,
        'v': '5.131'
    }
    try:
        response = requests.post(url, params=params, timeout=10)
        if response.status_code == 200:
            return response.json().get('response', {})
    except Exception as e:
        log(f"Исключение при сохранении фото: {str(e)}", color=ERROR_COLOR)
    return {}

def delete_last_wall_post(token):
    url = "https://api.vk.com/method/wall.get"
    params = {'access_token': token, 'v': '5.131', 'count': 1}
    try:
        response = requests.get(url, params=params, timeout=10)
        data = response.json()
        if response.status_code == 200 and 'response' in data:
            items = data['response'].get('items', [])
            if items:
                last_post_id = items[0]['id']
                log(f"Последняя запись найдена: ID {last_post_id}", color=INFO_COLOR)
                delete_url = "https://api.vk.com/method/wall.delete"
                delete_params = {'access_token': token, 'v': '5.131', 'post_id': last_post_id}
                delete_response = requests.post(delete_url, params=delete_params, timeout=10)
                if delete_response.status_code == 200:
                    log(f"Последняя запись успешно удалена: ID {last_post_id}", color=SUCCESS_COLOR)
                else:
                    log(f"Ошибка при удалении записи: {delete_response.text}", color=ERROR_COLOR)
            else:
                log("Нет записей для удаления.", color=INFO_COLOR)
        else:
            log(f"Ошибка получения записей со стены: {response.text}", color=ERROR_COLOR)
    except Exception as e:
        log(f"Исключение при удалении записи: {str(e)}", color=ERROR_COLOR)

def prepare_image(image_path):
    """Подготавливает изображение: делаем квадратным, добавляем рамку и сохраняем в PNG."""
    try:
        image = Image.open(image_path)
        if image.width != image.height:
            new_size = max(image.width, image.height)
            new_image = Image.new("RGBA", (new_size, new_size), (255, 255, 255, 0))
            new_image.paste(image, ((new_size - image.width) // 2, (new_size - image.height) // 2))
        else:
            new_image = image
        new_image_with_border = ImageOps.expand(new_image, border=border_size, fill=border_color)
        prepared_image_path = os.path.join(avatars_path, "prepared_image.png")
        new_image_with_border.save(prepared_image_path, format="PNG")
        return prepared_image_path
    except Exception as e:
        log(f"Ошибка подготовки изображения: {str(e)}", color=ERROR_COLOR)
        return None

# ----------------------------------------------------------------------------
# Основной рабочий цикл (поток)
# ----------------------------------------------------------------------------

def upload_avatar():
    global token_counter
    processed_count = 0  
    success_count = 0    
    fail_count = 0       

    while tokens and avatars:
        pause_event.wait()

        token_counter += 1
        token = tokens.pop(0)
        avatar_file = avatars.pop(0)

        processed_count += 1
        processed_photos.set(f"Обработано фотографий: {processed_count}")
        remaining_photos.set(f"Осталось фотографий: {len(avatars)}")

        user_info = get_user_info(token)
        user_name = f"{user_info.get('first_name', '')} {user_info.get('last_name', '')}" if user_info else "Неизвестный пользователь"

        log(f"{token_counter}. Токен (пользователь: {user_name}): {token}", color=INFO_COLOR)
        log(f"Обработка файла: {avatar_file}", color=INFO_COLOR)

        upload_url = get_upload_url(token)
        if not upload_url:
            log(f"Не удалось получить URL для загрузки для {user_name}.", color=ERROR_COLOR)
            fail_count += 1
            failed_uploads.set(f"Неуспешных добавлений: {fail_count}")
            log_fail_token(token)
            log_failed_account(token)
            try:
                os.remove(avatar_file)
            except Exception as e:
                log(f"Ошибка удаления файла: {str(e)}", color=ERROR_COLOR)
            delay_and_continue()
            continue

        upload_success = False
        response = None
        for attempt in range(1, 4):
            prepared_image_path = prepare_image(avatar_file)
            if not prepared_image_path:
                log(f"Попытка {attempt}: Не удалось подготовить изображение для {user_name}.", color=ERROR_COLOR)
                continue

            try:
                with open(prepared_image_path, 'rb') as file:
                    files = {'photo': file}
                    response = requests.post(upload_url, files=files, timeout=10)
                if response.status_code != 200:
                    log(f"Попытка {attempt}: Ошибка загрузки фото для {user_name}: {response.text}", color=ERROR_COLOR)
                else:
                    upload_success = True
                    break
            except Exception as e:
                log(f"Попытка {attempt}: Исключение при загрузке: {str(e)}", color=ERROR_COLOR)
            finally:
                if os.path.exists(prepared_image_path):
                    try:
                        os.remove(prepared_image_path)
                    except Exception as e:
                        log(f"Ошибка удаления подготовленного изображения: {str(e)}", color=ERROR_COLOR)
            if attempt < 3 and not upload_success:
                log(f"Повторная попытка загрузки фото для {user_name}...", color=INFO_COLOR)
                time.sleep(1)

        if not upload_success:
            log(f"Не удалось загрузить фото для {user_name} после 3 попыток.", color=ERROR_COLOR)
            fail_count += 1
            failed_uploads.set(f"Неуспешных добавлений: {fail_count}")
            log_fail_token(token)
            log_failed_account(token)
            try:
                os.remove(avatar_file)
            except Exception as e:
                log(f"Ошибка удаления неудачной фотографии: {str(e)}", color=ERROR_COLOR)
            delay_and_continue()
            continue

        log(f"Фото успешно загружено на сервер для {user_name}.", color=SUCCESS_COLOR)
        try:
            upload_data = response.json()
        except Exception as e:
            log(f"Ошибка преобразования ответа в JSON: {str(e)}", color=ERROR_COLOR)
            upload_data = {}
        save_response = save_photo(upload_data, token)
        if save_response.get("saved") == 1:
            log(f"Фото сохранено для {user_name}. ID поста: {save_response.get('post_id')}", color=SUCCESS_COLOR)
            delete_last_wall_post(token)
            success_count += 1
            successful_uploads.set(f"Успешных добавлений: {success_count}")
            log_success_token(token)
        else:
            log(f"Ошибка сохранения фото для {user_name}.", color=ERROR_COLOR)
            fail_count += 1
            failed_uploads.set(f"Неуспешных добавлений: {fail_count}")
            log_fail_token(token)
            log_failed_account(token)

        try:
            destination_path = os.path.join(uploaded_path, os.path.basename(avatar_file))
            shutil.move(avatar_file, destination_path)
        except Exception as e:
            log(f"Ошибка перемещения оригинального файла: {str(e)}", color=ERROR_COLOR)

        log("—" * 50, color=SEPARATOR_COLOR)
        delay_and_continue()

    log("Загрузка аватарок завершена. Все операции выполнены.", color=SUCCESS_COLOR)
    upload_button["state"] = "normal"
    pause_button["state"] = "disabled"
    resume_button["state"] = "disabled"

def delay_and_continue():
    try:
        min_delay_val = float(delay_min.get())
        max_delay_val = float(delay_max.get())
        delay = random.uniform(min_delay_val, max_delay_val)
        log(f"Задержка перед следующим аккаунтом: {delay:.2f} секунд", color=INFO_COLOR)
        time.sleep(delay)
    except ValueError:
        log("Ошибка в значениях задержки. Установлены значения по умолчанию: 2-3 секунды.", color=ERROR_COLOR)
        time.sleep(2)

def start_upload_thread():
    log("Нажата кнопка 'Запустить'", color=INFO_COLOR)
    upload_button["state"] = "disabled"
    pause_button["state"] = "normal"
    resume_button["state"] = "disabled"
    pause_event.set()
    threading.Thread(target=upload_avatar, daemon=True).start()

def pause_process():
    log("Нажата кнопка 'Приостановить'", color=INFO_COLOR)
    pause_event.clear()
    pause_button["state"] = "disabled"
    resume_button["state"] = "normal"

def resume_process():
    log("Нажата кнопка 'Восстановить'", color=INFO_COLOR)
    pause_event.set()
    resume_button["state"] = "disabled"
    pause_button["state"] = "normal"

# ----------------------------------------------------------------------------
# Функция очистки всех журналов
# ----------------------------------------------------------------------------

def clear_all_logs():
    log_text.delete(1.0, tk.END)
    success_text.delete(1.0, tk.END)
    fail_text.delete(1.0, tk.END)

# ----------------------------------------------------------------------------
# Контекстное меню для всех текстовых полей
# ----------------------------------------------------------------------------

last_clicked_text_widget = None

def create_context_menu(event):
    global last_clicked_text_widget
    last_clicked_text_widget = event.widget
    context_menu.tk_popup(event.x_root, event.y_root)

def copy_selected_text():
    global last_clicked_text_widget
    if not last_clicked_text_widget:
        return
    try:
        selected_text = last_clicked_text_widget.selection_get()
        root.clipboard_clear()
        root.clipboard_append(selected_text)
    except tk.TclError:
        pass

# ----------------------------------------------------------------------------
# Построение интерфейса
# ----------------------------------------------------------------------------

main_frame = ttk.Frame(root, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)

# Верхняя панель управления
top_frame = ttk.Frame(main_frame)
top_frame.pack(fill=tk.X, pady=5)

upload_button = ttk.Button(top_frame, text="Запустить", command=start_upload_thread, width=15)
upload_button.pack(side=tk.LEFT, padx=5)

pause_button = ttk.Button(top_frame, text="Приостановить", command=pause_process, width=15, state="disabled")
pause_button.pack(side=tk.LEFT, padx=5)

resume_button = ttk.Button(top_frame, text="Восстановить", command=resume_process, width=15, state="disabled")
resume_button.pack(side=tk.LEFT, padx=5)

update_button = ttk.Button(top_frame, text="Обновить информацию", command=update_info, width=20)
update_button.pack(side=tk.LEFT, padx=5)

delay_label = ttk.Label(top_frame, text="Задержка (сек):")
delay_label.pack(side=tk.LEFT, padx=(10, 5))

delay_min_entry = ttk.Entry(top_frame, textvariable=delay_min, width=5)
delay_min_entry.pack(side=tk.LEFT)

delay_dash_label = ttk.Label(top_frame, text="-")
delay_dash_label.pack(side=tk.LEFT)

delay_max_entry = ttk.Entry(top_frame, textvariable=delay_max, width=5)
delay_max_entry.pack(side=tk.LEFT)

clear_log_button = ttk.Button(top_frame, text="Очистить все журналы", command=clear_all_logs, width=18)
clear_log_button.pack(side=tk.LEFT, padx=5)

# Средняя панель статистики
stats_frame = ttk.Frame(main_frame)
stats_frame.pack(fill=tk.X, pady=5)
ttk.Label(stats_frame, textvariable=total_accounts).pack(anchor=tk.W)
ttk.Label(stats_frame, textvariable=processed_photos).pack(anchor=tk.W)
ttk.Label(stats_frame, textvariable=remaining_photos).pack(anchor=tk.W)
ttk.Label(stats_frame, textvariable=successful_uploads).pack(anchor=tk.W)
ttk.Label(stats_frame, textvariable=failed_uploads).pack(anchor=tk.W)

# Нижняя панель с тремя окнами: общий журнал, успешные токены, неуспешные токены
bottom_frame = ttk.Frame(main_frame)
bottom_frame.pack(fill=tk.BOTH, expand=True, pady=(5, 0))

bottom_frame.columnconfigure(0, weight=1)
bottom_frame.columnconfigure(1, weight=1)
bottom_frame.columnconfigure(2, weight=1)

# Общий журнал
log_frame = ttk.Frame(bottom_frame, relief=tk.SUNKEN)
log_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
ttk.Label(log_frame, text="Общий журнал").pack(anchor="n")
log_text = tk.Text(log_frame, height=15, wrap=tk.WORD, bg="#34495E", fg="white", font=default_font)
log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
log_scroll = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=log_text.yview)
log_scroll.pack(side=tk.RIGHT, fill=tk.Y)
log_text.config(yscrollcommand=log_scroll.set)

# Успешные токены
success_frame = ttk.Frame(bottom_frame, relief=tk.SUNKEN)
success_frame.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
ttk.Label(success_frame, text="Успешные токены").pack(anchor="n")
success_text = tk.Text(success_frame, height=15, wrap=tk.WORD, bg="#34495E", fg="white", font=default_font)
success_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
success_scroll = ttk.Scrollbar(success_frame, orient=tk.VERTICAL, command=success_text.yview)
success_scroll.pack(side=tk.RIGHT, fill=tk.Y)
success_text.config(yscrollcommand=success_scroll.set)

# Неуспешные токены
fail_frame = ttk.Frame(bottom_frame, relief=tk.SUNKEN)
fail_frame.grid(row=0, column=2, sticky="nsew", padx=5, pady=5)
ttk.Label(fail_frame, text="Неуспешные токены").pack(anchor="n")
fail_text = tk.Text(fail_frame, height=15, wrap=tk.WORD, bg="#34495E", fg="white", font=default_font)
fail_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)
fail_scroll = ttk.Scrollbar(fail_frame, orient=tk.VERTICAL, command=fail_text.yview)
fail_scroll.pack(side=tk.RIGHT, fill=tk.Y)
fail_text.config(yscrollcommand=fail_scroll.set)

# ----------------------------------------------------------------------------
# Контекстное меню для всех текстовых полей
# ----------------------------------------------------------------------------

context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="Копировать", command=copy_selected_text)
context_menu.add_separator()
context_menu.add_checkbutton(label="Автопрокрутка", variable=auto_scroll_enabled)

log_text.bind("<Button-3>", create_context_menu)
success_text.bind("<Button-3>", create_context_menu)
fail_text.bind("<Button-3>", create_context_menu)

# ----------------------------------------------------------------------------
# Запуск циклов обработки очередей и обновление информации
# ----------------------------------------------------------------------------

process_log_queue()
process_success_queue()
process_fail_queue()
update_info()

root.mainloop()
